
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.Xml;
using System.Collections;
using System.Threading;

using Swaf;
using Swaf.BizRule;
using Swaf.Config;
using Swaf.Call;
using Swaf.Container;

namespace Swaf.Scheduler
{
	/// <summary>
	///<p>Provides a base implementation of the <tt>IScheduleManager</tt> interface that is appropriate
	///to use in most hosting environments such as a standard java application and WebSphere app server.
	///This class uses an instance of the <tt>Timer</tt> subject from the "General" class factory to notify
	///it on a periodic basis in order to check for scheduled tasks ready to execute based on their schedule.
	///This allows different timer types of be used for different hosting environments while keeping the basic
	///scheduling logic in one class regardless of the hosting environment.</p>
	///<p>Scheduled tasks can be defined in the application configuration or added dynamically by calling
	///the <tt>TaskScheduleManager.scheduleTask</tt> method.  When a task is scheduled via the
	///application configuration, its format is:
	///<p class="MsoNormal" style='mso-layout-grid-align:none'><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">&lt;</span><span style="FONT-SIZE:8pt;COLOR:maroon;FONT-FAMILY:'Courier New'">Scheduler</span><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">&gt;<BR>
	///</span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='COLOR:blue'>&nbsp; 
	///&lt;</span><span style='COLOR:maroon'>Task</span><span style='COLOR:fuchsia'> </span>
	///<span style='COLOR:red'>id</span><span style='COLOR:blue'>="method_beginTest"</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:red'>frequency</span><span style='COLOR:blue'>="every 2.5 seconds"&gt;<BR>
	///</span></span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='COLOR:blue'>
	///&nbsp;&nbsp;&nbsp; &lt;</span><span style='COLOR:maroon'>CALL_CLASS_NICKNAME</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:red'>CALL_CLASS_ATTRIBUTES</span><span style='COLOR:blue'>="..."</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:blue'>/&gt;<BR>
	///</span></span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='COLOR:blue'>
	///&nbsp; &lt;/</span><span style='COLOR:maroon'>Task</span><span style='COLOR:blue'>&gt;<BR>
	///</span></span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='COLOR:blue'>
	///&nbsp; &lt;</span><span style='COLOR:maroon'>Task</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:red'>id</span><span style='COLOR:blue'>="method_beginTest2"</span><span style='COLOR:fuchsia'> </span><span style='COLOR:red'>frequency</span><span style='COLOR:blue'>="Every Tue at </span>
	///</span><st1:time Minute="1" Hour="14"><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">2:01pm</span></st1:time><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">"&gt;<BR>
	///</span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='mso-tab-count:1'>
	///&nbsp;&nbsp;&nbsp; </span><span style='COLOR:blue'>&lt;</span><span style='COLOR:maroon'>CALL_CLASS_NICKNAME</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:red'>CALL_CLASS_ATTRIBUTES</span><span style='COLOR:blue'>="..."</span><span style='COLOR:fuchsia'>
	///</span><span style='COLOR:blue'>/&gt;<BR>
	///</span></span><span style="FONT-SIZE:8pt;FONT-FAMILY:'Courier New'"><span style='mso-tab-count:1'>&nbsp; </span><span style='COLOR:blue'>&lt;/</span><span style='COLOR:maroon'>Task</span><span style='COLOR:blue'>&gt;<BR>
	///</span></span><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">
	///&lt;/</span><span style="FONT-SIZE:8pt;COLOR:maroon;FONT-FAMILY:'Courier New'">Scheduler</span><span style="FONT-SIZE:8pt;COLOR:blue;FONT-FAMILY:'Courier New'">&gt;</span>
	///</p>
	///Multiple <tt>Scheduler</tt> elements can be defined throught the application configuration.  Scheduled
	///tasks are defined with a child <tt>Task</tt> element.  This class uses the frequency attribute of the 
	///Task element as a pattern to be used to create objects that implement the <tt>IScheduledTask</tt>
	///interface using the <tt>IClassFactory.createFromPattern</tt> method.  This allows new implementations
	///of <tt>IShceduleTask</tt> to be used simply by updating the "ScheduleTaskTypes" class factory and
	///defining regular expression patterns that can be matched against text specified in the 
	///frequency attribute for the Task.  The id attribute of the Task element is used as the 
	///unique identifier when adding/removing tasks from the scheduler.</p>
	///<p>The CALL_CLASS_NICKNAME in the example above should be a name that can be matched as a nickname
	///from the "CallTypes" class factory.  The specific attributes used in the CALL_CLASS_NICKNAME element
	///are dependant on the specific call type being made.  See the <tt>CoresSrvices.Call</tt>
	///package for more details on the options each call type needs.</p>
	///
	///This class provides the base functionality for the ScheduleManager service which provides a mechanism 
	///for scheduling tasks to occur at some point in the future, either once or at regular intervals.  
	///All ScheduleManager implementations extend this class and must implement the specified abstract methods.
	/// </summary>
	public class ScheduleManager : IScheduleManager, IConfigPartner
	{
		protected IDictionary m_tasks;
		protected IList m_tasksToRemove;
		protected int m_timerInterval=1000;
		protected IApplication m_app;
		protected IClassFactory m_taskFactory;
		protected Timer m_scheduleTImer;
		protected ReaderWriterLock m_rootLock;
		//protected ICallbackQueue m_callbackQueue;
		protected bool m_isProcessing=false;
		static int s_WaitTimeout = 300;

		public ScheduleManager(IApplication app)
		{
			m_tasks = new Hashtable();
			m_tasksToRemove = new ArrayList();
			m_app = app;
			//m_callbackQueue = new ThreadedCallbackQueue("Scheduler");
            resetTimer();

            IAppManagement appMgmt = app as IAppManagement;
            appMgmt.OnAppShutdown += new AppShutdownRequest(OnAppShutdown);
		}

        public void OnAppShutdown(IApplication app)
        {
            // Close all tasks to clean up nicely
            clearAllTasks();
        }

		protected void resetTimer()
		{
			m_scheduleTImer = new System.Threading.Timer(new System.Threading.TimerCallback(processSchedule), null,0,m_timerInterval);
			m_isProcessing = true;
		}
		protected void processSchedule(object source)
		{
			if(!m_app.inBrowseMode)
			{
				IList tasksToRemove = new ArrayList();
				lock(m_tasks)
				{
					foreach(DictionaryEntry entry in m_tasks)
					{
						IScheduledTask task = (IScheduledTask) entry.Value;
						if (task.IsReady)
						{
							//m_callbackQueue.enqueueCall((ICall) task);
							m_app.ruleMgr.executeAsync("SchedulerCallTask", null, null, task);
							task.moveToNextRepeatTime();
							//If this is an execute once task, remove it from the list when done.
							if (! task.IsRepeatable)
							{
								if (tasksToRemove == null)
									tasksToRemove = new ArrayList();
								tasksToRemove.Add(task.Id);
							}

						}
					}
				}
				if (tasksToRemove.Count > 0)
				{
					foreach(string taskid in tasksToRemove)
						removeTask(taskid);
				}
			}
		}
		#region IScheduleManager Members

		public void clearAllTasks()
		{
			lock(m_tasks)
			{
				m_tasks.Clear();
			}
			stop();
		}

		public void removeTask(string taskId)
		{
			lock(m_tasks)
			{
				m_tasks.Remove(taskId);
			}
			if (m_tasks.Count == 0)
				stop();
		}

		public void stop()
		{
			m_scheduleTImer.Dispose();
			m_isProcessing = false;
		}

		public void createScheduledTask(string taskId, string taskInfo, ICall call)
		{
			if (m_taskFactory == null)
				m_taskFactory = (IClassFactory) m_app.factories["TaskTypes"];

			scheduleTask((IScheduledTask) m_taskFactory.createFromPattern(taskInfo, taskId, call));
		}

		void Swaf.Scheduler.IScheduleManager.createScheduledTask(string taskId, System.Xml.XmlElement configInfo)
		{
			if (m_taskFactory == null)
				m_taskFactory = (IClassFactory) m_app.factories["TaskTypes"];

			string frequency  = configInfo.GetAttribute("frequency").ToLower();
			scheduleTask((IScheduledTask) m_taskFactory.createFromPattern(frequency,taskId,configInfo));
		}

		public void scheduleTask(IScheduledTask task)
		{
			lock(m_tasks)
			{
				m_tasks[task.Id] = task; //Add(task.Id,task);
			}
			if (!m_isProcessing)
				resetTimer();
		}
		public int timerInterval
		{
			get{return m_timerInterval;}
			set
			{
				m_timerInterval = value;
				resetTimer();
			}
		}
		#endregion


		#region IConfigPartner Members

		public void checkConfigStatus()
		{
		}

		public void registerForConfig(IConfigManager config)
		{
			config.registerHandler("...<Scheduler>", new SchedulerConfigHandler(m_app,this));
			config.registerHandler("...<Scheduler> <Task>", new TaskConfigHandler(m_app, this, m_taskFactory));
			config.registerHandler("...<Scheduler> <UserTask>", new TaskConfigHandler(m_app, this, m_taskFactory));
		}
		protected internal class TaskConfigHandler : ConfigHandler
		{
			protected static string[] s_validAttributes = new string[]{"id", "frequency", "isSingleton"};
			protected IScheduleManager m_parent;
			protected IApplication m_app;
			protected IClassFactory m_tasksFactory;
			public TaskConfigHandler(IApplication app, IScheduleManager parent, IClassFactory tasksFactory)
			{
				m_app = app;
				m_parent = parent;
				m_tasksFactory=tasksFactory;
			}

			public override void executeBefore(IConfigElement configInfo)
			{
				configInfo.checkIllegalAttributes(s_validAttributes);
				XmlElement ele = configInfo.Element;
				m_parent.createScheduledTask(ele.GetAttribute("id"), ele);
				//m_parent.scheduleTask((IScheduledTask) m_tasksFactory.createFromNickName(configInfo.Element.Name, ele));
			}
		}
		protected internal class SchedulerConfigHandler : ConfigHandler
		{
			protected static string[] s_validAttributes = new string[]{"timerInterval"};
			protected IScheduleManager m_parent;
			public SchedulerConfigHandler(IApplication app, IScheduleManager parent)
			{
				m_parent = parent;
			}

			public override void executeBefore(IConfigElement configInfo)
			{
				configInfo.checkIllegalAttributes(s_validAttributes);
				XmlElement ele = configInfo.Element;
				string timerInterval = ele.GetAttribute("timerInterval");
				m_parent.timerInterval=timerInterval.Equals("")?1000 : int.Parse(timerInterval);
			}
		}

		#endregion

	}

	public class SchedulerRules
	{
		public void SchedulerCallTask(IApplication app, IRuleContext context, IScheduledTask task)
		{
			task.fireNow();
		}
	}

}
